const sceneCount = 12

function playScene() {
  ctx.camera = null

  const id = location.hash.slice(1)
  if (!id) return
  const constructor = eval('Scene' + id)
  const scene = new constructor()

  canvas.onChangeScene = scene.update ? function () {
    canvas.isPlaying = true
    canvas.play([scene])
  } : function () {
    canvas.draw([scene])
  }

  if (canvas.isPlaying) {
    canvas.isPlaying = false
  } else {
    canvas.onChangeScene()
    canvas.onChangeScene = function debug () {
      console.log(scene.data)
    }
  }
}

// draw pixel
class Scene01 {
  draw () {
    for (let i = 0; i < 100; ++i) {
      canvas.setPixel(i, i)
    }
  }
}

// ball
class Scene02 {
  constructor () {
    this.r = 10
    this.x = 200
    this.y = 200
    this.dx = 2
    this.dy = -2
  }
  draw (c) {
    c.beginPath()
    c.arc(this.x, this.y, this.r, 0, Math.PI*2)
    c.fill()
    //c.closePath()
  }
  update () {
    if (this.x + this.r >= width || this.x - this.r <= 0)
      this.dx = -this.dx
    if (this.y + this.r >= height || this.y - this.r <= 0) {
      this.dy = -this.dy
    }
    this.x += this.dx, this.y += this.dy
  }
}

// draw line
class Scene03 {
  draw (c) {
    c.fillText('拖拽鼠标绘制直线', 10, 20)
    const that = this
    canvas.onmousedown = function (e) {
      const [x0, y0] = canvas.fromClient(e)
      const imageData = ctx.getImageData(0, 0, width, height) // snapshot
      document.onmousemove = function (e) {
        ctx.putImageData(imageData, 0, 0) // restore
        let [x, y] = canvas.between(...canvas.fromClient(e))
        that.drawLinePixel(c, x0, y0, x, y)
        //that.drawLineNative(c, x0, y0, x, y)
      }
      document.onmouseup = function (e) {
        this.onmouseup = null
        this.onmousemove = null
      }
    }
  }
  drawLineNative (c, x0, y0, x, y) {
    c.beginPath()
    c.moveTo(x0, y0)
    c.lineTo(x, y)
    c.stroke()
  }
  drawLinePixel (c, x0, y0, x1, y1) {
    const dx = x1 - x0,
          dy = y1 - y0,
          adx = Math.abs(dx),
          ady = Math.abs(dy),
          vx = Math.sign(dx),
          vy = Math.sign(dy),
          color = Color.fromStr('#29f')
    let x = x0, y = y0
    if (adx >= ady) {
      let err = -adx // err = -0.5
      while (x !== x1) {
        //canvas.setPixel(x, y)
        const alpha = Math.abs(err) * 0.5 / adx
        canvas.setPixel(x, y, color.alpha(alpha))
        canvas.setPixel(x, y+vy, color.alpha(1-alpha))
        x += vx
        err += 2 * ady // err += ady / adx
        if (err >= 0) {
          y += vy
          err -= 2 * adx // err -= 1
        }
      }
    } else {
      let err = -ady
      while (y !== y1) {
        //canvas.setPixel(x, y)
        const alpha = Math.abs(err) * 0.5 / ady
        canvas.setPixel(x, y, color.alpha(alpha))
        canvas.setPixel(x+vx, y, color.alpha(1-alpha))
        y += vy
        err += 2 * adx
        if (err >= 0) {
          x += vx
          err -= 2 * ady
        }
      }
    }
  }
}

// transform2d
class Scene04 {
  constructor () {
    this.t = new Transform2D([
      [100, 100], [100, 150], [200, 150], [200, 100]
    ])
  }
  draw (c) {
    this.drawBox(c)
    this.t.translate(200, 100)
    this.drawBox(c)
    canvas.setPixel(200, 200)
    this.t.rotate(Math.PI/3, 200, 200)
    this.drawBox(c)
    this.t.scale(0.5, 0.5, 200, 200)
    this.drawBox(c)
    this.t.reflect(200, 200)
    this.drawBox(c)
  }
  drawBox (c) {
    const points = this.t.points
    c.beginPath()
    c.moveTo(points[0][0], points[0][1])
    for (let i = 1; i < points.length; ++i) {
      c.lineTo(points[i][0], points[i][1])
    }
    c.closePath()
    c.stroke()
  }
}

// transform3d
class Scene05 {
  constructor () {
    //this.initCube()
    this.initStamp()
  }
  initCube () {
    this.t = new Transform3D([
      [0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1],
      [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]
    ])
    this.edges = [
      [0, 1], [0, 2], [0, 4], [1, 3],
      [1, 5], [2, 6], [2, 3], [3, 7],
      [4, 5], [4, 6], [5, 7], [6, 7]
    ]
    this.t.scale(200)
    this.t.translate(175, 100, -100)
  }
  initStamp () {
    const s = Math.sqrt(2)
    this.t = new Transform3D([
      [1, 1, -1-s], [-1, 1, -1-s], [-1, -1, -1-s], [1, -1, -1-s],
      [1, 1, 1+s], [-1, 1, 1+s], [-1, -1, 1+s], [1, -1, 1+s],
      [1, -1-s, 1], [-1, -1-s, 1], [-1, -1-s, -1], [1, -1-s, -1],
      [1, 1+s, 1], [-1, 1+s, 1], [-1, 1+s, -1], [1, 1+s, -1],
      [-1-s, 1, 1], [-1-s, -1, 1], [-1-s, -1, -1], [-1-s, 1, -1],
      [1+s, 1, 1], [1+s, -1, 1], [1+s, -1, -1], [1+s, 1, -1],
    ])
    this.edges = [
      [0, 1], [1, 2], [2, 3], [3, 0],
      [4, 5], [5, 6], [6, 7], [7, 4],
      [8, 9], [9, 10], [10, 11], [11, 8],
      [12, 13], [13, 14], [14, 15], [15, 12],
      [16, 17], [17, 18], [18, 19], [19, 16],
      [20, 21], [21, 22], [22, 23], [23, 20],

      [4, 12], [12, 20], [20, 4], // +++
      [0, 23], [23, 15], [15, 0], // ++-
      [7, 8], [8, 21], [21, 7], // +-+
      [22, 11], [11, 3], [3, 22], // +--
      [16, 5], [5, 13], [13, 16], // -++
      [1, 14], [14, 19], [19, 1], // -+-
      [6, 9], [9, 17], [17, 6], // --+
      [2, 10], [10, 18], [18, 2], // ---
    ]
    this.t.scale(50)
    this.t.translate(275, 200, 0)
    ctx.camera = new Camera()
  }
  draw (c) {
    const points = this.t.points
    this.edges.forEach(function (e) {
      c.beginPath()
      c.moveTo(points[e[0]][0], points[e[0]][1])
      c.lineTo(points[e[1]][0], points[e[1]][1])
      //c.moveTo(...c.camera.shot(points[e[0]]))
      //c.lineTo(...c.camera.shot(points[e[1]]))
      c.stroke()
    })
  }
  update () {
    this.t.rotateX(0.01, height/2)
    this.t.rotateY(0.02, 0, width/2)
  }
}

// bezier
class Scene06 {
  constructor () {
    this.points = [
      [100, 100], [100, 300], [300, 300], [300, 100]
    ]
    this.curve = bezier(this.points)
  }
  draw (c) {
    this.drawBezier(c)

    // drag control points
    const that = this
    canvas.onmousedown = function (e) {
      let index = that.findNearest(e)
      document.onmousemove = function (e) {
        that.points[index] = canvas.fromClient(e)
        canvas.clear()
        that.drawBezier(c)
      }
      document.onmouseup = function (e) {
        this.onmouseup = null
        this.onmousemove = null
      }
    }
  }
  drawBezier (c) {
    c.fillText('试着拖拽控制点', 10, 20)
    // path
    c.beginPath()
    c.moveTo(this.points[0][0], this.points[0][1])
    for (let i = 1; i < this.points.length; ++i) {
      c.lineTo(this.points[i][0], this.points[i][1])
    }
    c.stroke()

    // points
    this.points.forEach(function (p) {
      c.beginPath()
      c.arc(p[0], p[1], 3, 0, 2 * Math.PI)
      c.fill()
    })

    // curve
    let p = this.curve(0)
    c.save()
    c.strokeStyle = '#ff8'
    c.beginPath()
    c.moveTo(...p)
    for (let t = 0; t <= 1; t += 0.01) {
      c.lineTo(...this.curve(t)) // 以直代曲
    }
    c.stroke()
    c.restore()
  }
  findNearest (e) {
    const [x, y] = canvas.fromClient(e)
    let minDist = Infinity
    let index = 0
    for (let i = 0; i < this.points.length; ++i) {
      let p = this.points[i]
      let dist = Math.hypot(x-p[0], y-p[1])
      if (dist < minDist) {
        minDist = dist
        index = i
      }
    }
    return index
  }
}

// bezier mesh
class Scene07 {
  constructor () {
    this.pointMat = [
      [ [100, 100, 200], [180, 100, 200], [260, 100, 200], [340, 100, 200] ],
      [ [100, 180, 200], [180, 180, 0], [260, 180, 0], [340, 180, 200] ],
      [ [100, 260, 200], [180, 260, 0], [260, 260, 0], [340, 260, 200] ],
      [ [100, 340, 200], [180, 340, 200], [260, 340, 200], [340, 340, 200] ],
    ]
    this.mesh = new BezierMesh(this.pointMat)
    this.mesh.transform(t => {
      t.rotateX(-1.2, height/2)
      t.translate(55, -150, 0)
    })
  }
  draw (c) {
    this.mesh.draw(c, 4)
  }
  update () {
    this.mesh.transform(t => {
      t.rotateX(-Math.PI/2+1.2, height/2)
      t.rotateY(0.02, 0, width/2)
      t.rotateX(Math.PI/2-1.2, height/2)
    })
  }
}

// revolution mesh
class Scene08 {
  constructor () {
    this.points = [
      [140, 100], [200, 180], [200, 260], [100, 340]
    ]
    this.mesh = new RevolutionMesh(this.points)
  }
  draw (c) {
    this.mesh.draw(c, 3)
  }
  update () {
    this.mesh.transform(t => {
      t.rotateY(0.02, 0, width/2)
    })
  }
}

// sphere
class Scene09 {
  constructor () {
    const r = 150
    const magic = 82.8427
    this.northPoints = [
      [width / 2, height / 2 - r],
      [width / 2 - magic, height / 2 - r],
      [width / 2 - r, height / 2 - magic],
      [width / 2 - r, height / 2],
    ]
    this.southPoints = [
      [width / 2 - r, height / 2],
      [width / 2 - r, height / 2 + magic],
      [width / 2 - magic, height / 2 + r],
      [width / 2, height / 2 + r],
    ]
    this.north = new RevolutionMesh(this.northPoints) // 北半球
    this.south = new RevolutionMesh(this.southPoints) // 南半球
    ctx.camera = new Camera() // 开启透视投影
    //ctx.camera.xray = true // 开启 x 光
  }
  draw (c) {
    this.north.draw(c, 3)
    this.south.draw(c, 3)
  }
  update () {
    const callback = t => {
      t.rotateY(0.02, 0, width/2)
    }
    this.north.transform(callback)
    this.south.transform(callback)
  }
}

// shade
class Scene10 {
  constructor () {
    this.initCube()
    ctx.camera = new Camera()
  }
  initCube () {
    this.points = [
      [0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1],
      [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]
    ]
    this.colors = this.points.map(p =>
      new Color(...p.map(x => x * 255))
    )
    this.t = new Transform3D(this.points)
    this.faces = [
      [0, 1, 3, 2], [4, 6, 7, 5], [0, 4, 5, 1],
      [2, 3, 7, 6], [0, 2, 6, 4], [1, 5, 7, 3]
    ]
    this.t.scale(70)
    this.t.translate(225, 150, -100)
  }
  draw (c) {
    const points = this.t.points
    this.faces.forEach(f => {
      if (c.camera.visible(points[f[0]], points[f[1]], points[f[2]], points[f[3]])) {
        const getPoint = i => {
          let ret = c.camera.shot(points[f[i]])
          ret.color = this.colors[f[i]]
          return ret
        }
        new TriangleShader([0, 1, 2].map(getPoint)).draw()
        new TriangleShader([2, 3, 0].map(getPoint)).draw()
      }
    })
  }
  update () {
    this.t.rotateX(0.1, height/2)
    this.t.rotateY(0.2, 0, width/2)
  }
}

// faster shade
class Scene11 extends Scene10 {
  draw (c) {
    const points = this.t.points
    this.faces.forEach(f => {
      if (c.camera.visible(points[f[0]], points[f[1]], points[f[2]], points[f[3]])) {
        const ps = [0, 1, 2, 3].map(i => {
          let ret = c.camera.shot(points[f[i]])
          ret.color = this.colors[f[i]]
          return ret
        })
        let grad = c.createLinearGradient(
          ps[0][0], ps[0][1], ps[1][0], ps[1][1]
        )
        grad.addColorStop(0, ps[0].color.toStr())
        grad.addColorStop(1, ps[1].color.toStr())

        const saveStyle = c.fillStyle
        c.fillStyle = grad
        c.beginPath()
        c.moveTo(...ps[0])
        c.lineTo(...ps[1])
        c.lineTo(...ps[2])
        c.lineTo(...ps[3])
        c.fill()
        c.fillStyle = saveStyle
      }
    })
  }
  update () {
    this.t.rotateX(0.01, height/2)
    this.t.rotateY(0.02, 0, width/2)
  }
}

class Scene12 {
  constructor () {
    this.points = [
      [-2, -1, -10], [-2, -1, 10], [-2, 1, -10], [-2, 1, 10],
      [2, -1, -10], [2, -1, 10], [2, 1, -10], [2, 1, 10],

      [-1, -10, -2], [-1, -10, 2], [-1, 10, -2], [-1, 10, 2],
      [1, -10, -2], [1, -10, 2], [1, 10, -2], [1, 10, 2],

      [-10, -2, -1], [-10, -2, 1], [-10, 2, -1], [-10, 2, 1],
      [10, -2, -1], [10, -2, 1], [10, 2, -1], [10, 2, 1]
    ]
    this.faces = [
      [0, 1, 3, 2], [4, 6, 7, 5], [0, 4, 5, 1],
      [2, 3, 7, 6], [0, 2, 6, 4], [1, 5, 7, 3],

      [8, 9, 11, 10], [12, 14, 15, 13], [8, 12, 13, 9],
      [10, 11, 15, 14], [8, 10, 14, 12], [9, 13, 15, 11],

      [16, 17, 19, 18], [20, 22, 23, 21], [16, 20, 21, 17],
      [18, 19, 23, 22], [16, 18, 22, 20], [17, 21, 23, 19]
    ]
    this.colors = this.points.map(p =>
      new Color(...p.map(x => x * 255))
    )
    this.t = new Transform3D(this.points)
    this.t.scale(15)
    this.t.translate(225, 150, -100)
    ctx.camera = new Camera()
  }
  draw (c) {
    const points = this.t.points
    this.faces.forEach(f => {
      if (c.camera.visible(points[f[0]], points[f[1]], points[f[2]], points[f[3]])) {
        const ps = [0, 1, 2, 3].map(i => {
          let ret = c.camera.shot(points[f[i]])
          ret.color = this.colors[f[i]]
          return ret
        })
        let grad = c.createLinearGradient(
          ps[0][0], ps[0][1], ps[1][0], ps[1][1]
        )
        grad.addColorStop(0, ps[0].color.toStr())
        grad.addColorStop(1, ps[1].color.toStr())

        const saveStyle = c.fillStyle
        c.fillStyle = grad
        c.beginPath()
        c.moveTo(...ps[0])
        c.lineTo(...ps[1])
        c.lineTo(...ps[2])
        c.lineTo(...ps[3])
        c.fill()
        c.fillStyle = saveStyle
      }
    })
  }
  update () {
    this.t.rotateX(0.01, height/2)
    this.t.rotateY(0.02, 0, width/2)
  }
}
